| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- import { PermissionAction } from '@supabase/shared-types/out/constants'
- import { IS_PLATFORM, useFlag, useParams } from 'common'
- import { ChevronDown } from 'lucide-react'
- import { cloneElement, useState } from 'react'
- import { toast } from 'sonner'
- import {
- Alert,
- Button,
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
- } from 'ui'
- import { GenericSkeletonLoader } from 'ui-patterns'
- import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
- import { LogDrainDestinationSheetForm } from '@/components/interfaces/LogDrains/LogDrainDestinationSheetForm'
- import { LogDrains } from '@/components/interfaces/LogDrains/LogDrains'
- import {
- LOG_DRAIN_TYPES,
- LogDrainType,
- } from '@/components/interfaces/LogDrains/LogDrains.constants'
- import DefaultLayout from '@/components/layouts/DefaultLayout'
- import { PageLayout } from '@/components/layouts/PageLayout/PageLayout'
- import SettingsLayout from '@/components/layouts/ProjectSettingsLayout/SettingsLayout'
- import { ScaffoldContainer, ScaffoldSection } from '@/components/layouts/Scaffold'
- import { DocsButton } from '@/components/ui/DocsButton'
- import {
- LogDrainCreateVariables,
- useCreateLogDrainMutation,
- } from '@/data/log-drains/create-log-drain-mutation'
- import { LogDrainData, useLogDrainsQuery } from '@/data/log-drains/log-drains-query'
- import { useUpdateLogDrainMutation } from '@/data/log-drains/update-log-drain-mutation'
- import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
- import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
- import { DOCS_URL } from '@/lib/constants'
- import type { NextPageWithLayout } from '@/types'
- const LogDrainsSettings: NextPageWithLayout = () => {
- const { can: canManageLogDrains, isLoading: isLoadingPermissions } = useAsyncCheckPermissions(
- PermissionAction.ANALYTICS_ADMIN_WRITE,
- 'logflare'
- )
- const [open, setOpen] = useState(false)
- const { ref } = useParams() as { ref: string }
- const [selectedLogDrain, setSelectedLogDrain] = useState<Partial<LogDrainData> | null>(null)
- const [isCreateConfirmModalOpen, setIsCreateConfirmModalOpen] = useState(false)
- const [pendingLogDrainValues, setPendingLogDrainValues] =
- useState<LogDrainCreateVariables | null>(null)
- const [mode, setMode] = useState<'create' | 'update'>('create')
- const { hasAccess: hasAccessToLogDrains, isLoading: isLoadingEntitlement } =
- useCheckEntitlements('log_drains')
- const sentryEnabled = useFlag('SentryLogDrain')
- const s3Enabled = useFlag('S3logdrain')
- const axiomEnabled = useFlag('axiomLogDrain')
- const otlpEnabled = useFlag('otlpLogDrain')
- const last9Enabled = useFlag('Last9LogDrain')
- const syslogEnabled = useFlag('syslogLogDrain')
- const { data: logDrains } = useLogDrainsQuery(
- { ref },
- { enabled: !isLoadingEntitlement && hasAccessToLogDrains }
- )
- const { mutate: createLogDrain, isPending: createLoading } = useCreateLogDrainMutation({
- onSuccess: () => {
- toast.success('Log drain destination created')
- setIsCreateConfirmModalOpen(false)
- setOpen(false)
- },
- onError: () => {
- toast.error('Failed to create log drain')
- setIsCreateConfirmModalOpen(false)
- setOpen(false)
- },
- })
- const { mutate: updateLogDrain, isPending: updateLoading } = useUpdateLogDrainMutation({
- onSuccess: () => {
- toast.success('Log drain updated')
- setOpen(false)
- },
- onError: () => {
- setOpen(false)
- toast.error('Failed to update log drain')
- },
- })
- const isLoading = createLoading || updateLoading
- function handleUpdateClick(drain: LogDrainData) {
- setSelectedLogDrain(drain)
- setMode('update')
- setOpen(true)
- }
- function handleNewClick(src: LogDrainType) {
- setSelectedLogDrain({ type: src })
- setMode('create')
- setOpen(true)
- }
- const content = (
- <ScaffoldSection isFullWidth id="log-drains" className="gap-6">
- <ScaffoldContainer className="flex flex-col gap-10" bottomPadding>
- <LogDrainDestinationSheetForm
- mode={mode}
- open={open}
- onOpenChange={(v) => {
- if (!v) {
- setSelectedLogDrain(null)
- }
- setOpen(v)
- }}
- defaultValues={{
- ...selectedLogDrain,
- type: selectedLogDrain?.type ? selectedLogDrain.type : 'webhook',
- }}
- isLoading={isLoading}
- onSubmit={({ name, description, type, ...values }) => {
- const logDrainValues = {
- name,
- description: description || '',
- type,
- config: values as any, // TODO: fix generated API types from backend
- id: selectedLogDrain?.id,
- projectRef: ref,
- token: selectedLogDrain?.token,
- }
- if (mode === 'create') {
- setPendingLogDrainValues(logDrainValues)
- setIsCreateConfirmModalOpen(true)
- } else {
- if (!logDrainValues.id || !selectedLogDrain?.token) {
- throw new Error('Log drain ID and token is required')
- } else {
- updateLogDrain(logDrainValues)
- }
- }
- }}
- />
- {isLoadingPermissions ? (
- <GenericSkeletonLoader />
- ) : !canManageLogDrains ? (
- <Alert variant="default">You do not have permission to manage log drains</Alert>
- ) : (
- <LogDrains onUpdateDrainClick={handleUpdateClick} onNewDrainClick={handleNewClick} />
- )}
- </ScaffoldContainer>
- <ConfirmationModal
- confirmLabel="Add destination"
- variant="default"
- title="Confirm Log Drain Creation"
- visible={isCreateConfirmModalOpen}
- onConfirm={() => {
- if (pendingLogDrainValues) {
- createLogDrain(pendingLogDrainValues)
- setPendingLogDrainValues(null)
- }
- setIsCreateConfirmModalOpen(false)
- }}
- onCancel={() => {
- setIsCreateConfirmModalOpen(false)
- setPendingLogDrainValues(null)
- }}
- >
- <div className="text-foreground-light text-sm space-y-2">
- <p>
- You are about to create a new log drain destination:{' '}
- <span className="text-foreground">{pendingLogDrainValues?.name}</span>
- </p>
- {IS_PLATFORM && (
- <p>
- This will incur an additional <span className="text-foreground">$60 per month</span>{' '}
- charge to your subscription.
- </p>
- )}
- <p>Are you sure you want to proceed?</p>
- </div>
- </ConfirmationModal>
- </ScaffoldSection>
- )
- // [kemal]: Ordinarily <PageLayout /> would be bundled with the getLayout function below, however in this case we need access to some bits for the "Add destination" button to render as part of the in-built page header in <PageLayout />.
- if (!isLoadingEntitlement && hasAccessToLogDrains) {
- return (
- <PageLayout
- title="log drains"
- subtitle="send your project logs to third party destinations"
- primaryActions={
- <>
- {!(logDrains?.length === 0) && (
- <div className="flex items-center">
- <Button
- disabled={!hasAccessToLogDrains || !canManageLogDrains}
- onClick={() => {
- setSelectedLogDrain(null)
- setMode('create')
- setOpen(true)
- }}
- type="primary"
- className="rounded-r-none px-3"
- >
- Add destination
- </Button>
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button
- type="primary"
- title="Choose token scope"
- className="rounded-l-none px-[4px] py-[5px]"
- icon={<ChevronDown />}
- />
- </DropdownMenuTrigger>
- <DropdownMenuContent align="end" side="bottom">
- {LOG_DRAIN_TYPES.filter((t) => {
- if (t.value === 'sentry') return sentryEnabled
- if (t.value === 's3') return s3Enabled
- if (t.value === 'axiom') return axiomEnabled
- if (t.value === 'otlp') return otlpEnabled
- if (t.value === 'last9') return last9Enabled
- if (t.value === 'syslog') return syslogEnabled
- return true
- }).map((drainType) => (
- <DropdownMenuItem
- key={drainType.value}
- onClick={() => handleNewClick(drainType.value)}
- >
- <div className="flex items-center gap-3">
- {cloneElement(drainType.icon, { height: 16, width: 16 })}
- <div className="space-y-1">
- <p className="block text-foreground">{drainType.name}</p>
- {IS_PLATFORM && (
- <p className="text-xs text-foreground-lighter">Additional $60</p>
- )}
- </div>
- </div>
- </DropdownMenuItem>
- ))}
- </DropdownMenuContent>
- </DropdownMenu>
- </div>
- )}
- </>
- }
- secondaryActions={<DocsButton href={`${DOCS_URL}/guides/platform/log-drains`} />}
- >
- {content}
- </PageLayout>
- )
- }
- return content
- }
- LogDrainsSettings.getLayout = (page) => (
- <DefaultLayout>
- <SettingsLayout title="log drains">{page}</SettingsLayout>
- </DefaultLayout>
- )
- export default LogDrainsSettings
|